home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / lib / sunbird / js / calAlarmService.js next >
Encoding:
Text File  |  2007-05-23  |  17.2 KB  |  492 lines

  1. /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Oracle Corporation code.
  16.  *
  17.  * The Initial Developer of the Original Code is Oracle Corporation
  18.  * Portions created by the Initial Developer are Copyright (C) 2005
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Stuart Parmenter <stuart.parmenter@oracle.com>
  23.  *   Joey Minta <jminta@gmail.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. const kHoursBetweenUpdates = 6;
  40.  
  41. function newTimerWithCallback(callback, delay, repeating)
  42. {
  43.     var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  44.     
  45.     timer.initWithCallback(callback,
  46.                            delay,
  47.                            (repeating) ? timer.TYPE_REPEATING_PRECISE : timer.TYPE_ONE_SHOT);
  48.     return timer;
  49. }
  50.  
  51. function calAlarmService() {
  52.     this.wrappedJSObject = this;
  53.  
  54.     this.calendarObserver = {
  55.         alarmService: this,
  56.  
  57.         // calIObserver:
  58.         onStartBatch: function() { },
  59.         onEndBatch: function() { },
  60.         onLoad: function() { },
  61.         onAddItem: function(aItem) {
  62.             var occs = [];
  63.             if (aItem.recurrenceInfo) {
  64.                 var start = this.alarmService.mRangeEnd.clone();
  65.                 // We search 1 month in each direction for alarms.  Therefore,
  66.                 // we need to go back 2 months from the end to get this right.
  67.                 start.month -= 2;
  68.                 start.normalize();
  69.                 occs = aItem.recurrenceInfo.getOccurrences(start, this.alarmService.mRangeEnd, 0, {});
  70.             } else {
  71.                 occs = [aItem];
  72.             }
  73.             function hasAlarm(a) {
  74.                 return a.alarmOffset || a.parentItem.alarmOffset;
  75.             }
  76.             occs = occs.filter(hasAlarm);
  77.             for each (var occ in occs) {
  78.                 this.alarmService.addAlarm(occ);
  79.             }
  80.         },
  81.         onModifyItem: function(aNewItem, aOldItem) {
  82.             this.alarmService.removeAlarm(aOldItem);
  83.  
  84.             this.onAddItem(aNewItem);
  85.         },
  86.         onDeleteItem: function(aDeletedItem) {
  87.             this.alarmService.removeAlarm(aDeletedItem);
  88.         },
  89.         onError: function(aErrNo, aMessage) { }
  90.     };
  91.  
  92.  
  93.     this.calendarManagerObserver = {
  94.         alarmService: this,
  95.  
  96.         onCalendarRegistered: function(aCalendar) {
  97.             this.alarmService.observeCalendar(aCalendar);
  98.         },
  99.         onCalendarUnregistering: function(aCalendar) {
  100.             this.alarmService.unobserveCalendar(aCalendar);
  101.         },
  102.         onCalendarDeleting: function(aCalendar) {},
  103.         onCalendarPrefSet: function(aCalendar, aName, aValue) {},
  104.         onCalendarPrefDeleting: function(aCalendar, aName) {}
  105.     };
  106. }
  107.  
  108. var calAlarmServiceClassInfo = {
  109.     getInterfaces: function (count) {
  110.         var ifaces = [
  111.             Components.interfaces.nsISupports,
  112.             Components.interfaces.calIAlarmService,
  113.             Components.interfaces.nsIObserver,
  114.             Components.interfaces.nsIClassInfo
  115.         ];
  116.         count.value = ifaces.length;
  117.         return ifaces;
  118.     },
  119.  
  120.     getHelperForLanguage: function (language) {
  121.         return null;
  122.     },
  123.  
  124.     contractID: "@mozilla.org/calendar/alarm-service;1",
  125.     classDescription: "Calendar Alarm Service",
  126.     classID: Components.ID("{7a9200dd-6a64-4fff-a798-c5802186e2cc}"),
  127.     implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
  128.     flags: 0
  129. };
  130.  
  131. calAlarmService.prototype = {
  132.     mRangeEnd: null,
  133.     mEvents: {},
  134.     mObservers: [],
  135.     mUpdateTimer: null,
  136.     mStarted: false,
  137.  
  138.     QueryInterface: function (aIID) {
  139.         if (aIID.equals(Components.interfaces.nsIClassInfo))
  140.             return calAlarmServiceClassInfo;
  141.  
  142.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  143.             !aIID.equals(Components.interfaces.calIAlarmService) &&
  144.             !aIID.equals(Components.interfaces.nsIObserver))
  145.         {
  146.             throw Components.results.NS_ERROR_NO_INTERFACE;
  147.         }
  148.  
  149.         return this;
  150.     },
  151.  
  152.  
  153.     /* nsIObserver */
  154.     observe: function (subject, topic, data) {
  155.         if (topic == "profile-after-change") {
  156.             this.shutdown();
  157.             this.startup();
  158.         }
  159.         if (topic == "xpcom-shutdown") {
  160.             this.shutdown();
  161.         }
  162.     },
  163.  
  164.     /* calIAlarmService APIs */
  165.     mTimezone: null,
  166.     get timezone() {
  167.         return this.mTimezone;
  168.     },
  169.  
  170.     set timezone(aTimezone) {
  171.         this.mTimezone = aTimezone;
  172.     },
  173.  
  174.     snoozeEvent: function(event, duration) {
  175.         /* modify the event for a new alarm time */
  176.         // Make sure we're working with the parent, otherwise we'll accidentally
  177.         // create an exception
  178.         var newEvent = event.parentItem.clone();
  179.         var alarmTime = jsDateToDateTime((new Date())).getInTimezone("UTC");
  180.  
  181.         // Set the last acknowledged time to now.
  182.         newEvent.alarmLastAck = alarmTime;
  183.  
  184.         alarmTime = alarmTime.clone();
  185.         alarmTime.addDuration(duration);
  186.  
  187.         if (event.parentItem != event) {
  188.             // This is the *really* hard case where we've snoozed a single
  189.             // instance of a recurring event.  We need to not only know that
  190.             // there was a snooze, but also which occurrence was snoozed.  Part
  191.             // of me just wants to create a local db of snoozes here...
  192.             newEvent.setProperty("X-MOZ-SNOOZE-TIME-"+event.recurrenceId.nativeTime, alarmTime.icalString);
  193.         } else {
  194.             newEvent.setProperty("X-MOZ-SNOOZE-TIME", alarmTime.icalString);
  195.         }
  196.         // calling modifyItem will cause us to get the right callback
  197.         // and update the alarm properly
  198.         newEvent.calendar.modifyItem(newEvent, event.parentItem, null);
  199.     },
  200.  
  201.     addObserver: function(aObserver) {
  202.         dump("observer added\n");
  203.         if (this.mObservers.indexOf(aObserver) != -1)
  204.             return;
  205.  
  206.         this.mObservers.push(aObserver);
  207.     },
  208.  
  209.     removeObserver: function(aObserver) {
  210.         dump("observer removed\n");
  211.         function notThis(v) {
  212.             return v != aObserver;
  213.         }
  214.  
  215.         this.mObservers = this.mObservers.filter(notThis);
  216.     },
  217.  
  218.  
  219.     /* helper functions */
  220.     notifyObservers: function(functionName, args) {
  221.         function notify(obs) {
  222.             try { obs[functionName].apply(obs, args);  }
  223.             catch (e) { }
  224.         }
  225.         this.mObservers.forEach(notify);
  226.     },
  227.  
  228.     hasAlarm: function almSvc_hasAlarm(aItem) {
  229.         var hasSnooze;
  230.         if (aItem.parentItem != aItem) {
  231.             hasSnooze = aItem.parentItem.hasProperty("X-MOZ-SNOOZE-TIME-"+aItem.recurrenceId.nativeTime);
  232.         } else {
  233.             hasSnooze = aItem.hasProperty("X-MOZ-SNOOZE-TIME");
  234.         }
  235.  
  236.         return aItem.alarmOffset || aItem.parentItem.alarmOffset || hasSnooze;
  237.     },
  238.  
  239.     startup: function() {
  240.         if (this.mStarted)
  241.             return;
  242.  
  243.         if (!this.mTimezone) {
  244.             throw Components.results.NS_ERROR_NOT_INITIALIZED;
  245.         }
  246.  
  247.         dump("Starting calendar alarm service\n");
  248.  
  249.         var observerSvc = Components.classes["@mozilla.org/observer-service;1"]
  250.                           .getService
  251.                           (Components.interfaces.nsIObserverService);
  252.  
  253.         observerSvc.addObserver(this, "profile-after-change", false);
  254.         observerSvc.addObserver(this, "xpcom-shutdown", false);
  255.  
  256.         /* Tell people that we're alive so they can start monitoring alarms.
  257.          * Make sure to do this before calling findAlarms().
  258.          */
  259.         this.notifier = Components.classes["@mozilla.org/embedcomp/appstartup-notifier;1"].getService(Components.interfaces.nsIObserver);
  260.         var notifier = this.notifier;
  261.         notifier.observe(null, "alarm-service-startup", null);
  262.  
  263.         this.calendarManager = Components.classes["@mozilla.org/calendar/manager;1"].getService(Components.interfaces.calICalendarManager);
  264.         var calendarManager = this.calendarManager;
  265.         calendarManager.addObserver(this.calendarManagerObserver);
  266.  
  267.         var calendars = calendarManager.getCalendars({});
  268.         for each(var calendar in calendars) {
  269.             this.observeCalendar(calendar);
  270.         }
  271.  
  272.         this.findAlarms();
  273.  
  274.         /* set up a timer to update alarms every N hours */
  275.         var timerCallback = {
  276.             alarmService: this,
  277.             notify: function(timer) {
  278.                 this.alarmService.findAlarms();
  279.             }
  280.         };
  281.  
  282.         this.mUpdateTimer = newTimerWithCallback(timerCallback, kHoursBetweenUpdates * 3600000, true);
  283.  
  284.         this.mStarted = true;
  285.     },
  286.  
  287.     shutdown: function() {
  288.         /* tell people that we're no longer running */
  289.         var notifier = this.notifier;
  290.         notifier.observe(null, "alarm-service-shutdown", null);
  291.  
  292.         if (this.mUpdateTimer) {
  293.             this.mUpdateTimer.cancel();
  294.             this.mUpdateTimer = null;
  295.         }
  296.         
  297.         var calendarManager = this.calendarManager;
  298.         calendarManager.removeObserver(this.calendarManagerObserver);
  299.  
  300.         for each(var timer in this.mEvents) {
  301.             timer.cancel();
  302.         }
  303.         this.mEvents = {};
  304.  
  305.         var calendars = calendarManager.getCalendars({});
  306.         for each(var calendar in calendars) {
  307.             this.unobserveCalendar(calendar);
  308.         }
  309.  
  310.         this.calendarManager = null;
  311.         this.notifier = null;
  312.         this.mRangeEnd = null;
  313.  
  314.         var observerSvc = Components.classes["@mozilla.org/observer-service;1"]
  315.                           .getService
  316.                           (Components.interfaces.nsIObserverService);
  317.  
  318.         observerSvc.removeObserver(this, "profile-after-change");
  319.         observerSvc.removeObserver(this, "xpcom-shutdown");
  320.  
  321.         this.mStarted = false;
  322.     },
  323.  
  324.  
  325.     observeCalendar: function(calendar) {
  326.         calendar.addObserver(this.calendarObserver);
  327.     },
  328.  
  329.     unobserveCalendar: function(calendar) {
  330.         calendar.removeObserver(this.calendarObserver);
  331.     },
  332.  
  333.     addAlarm: function(aItem) {
  334.         var alarmTime;
  335.         if (aItem.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
  336.             alarmTime = aItem.startDate || aItem.entryDate || aItem.dueDate;
  337.         } else {
  338.             alarmTime = aItem.endDate || aItem.dueDate || aItem.entryDate;
  339.         }
  340.  
  341.         if (!alarmTime) {
  342. dump("Error: Could not determine alarm time for item '"+aItem.title+"'\n");
  343.             return;
  344.         }
  345.  
  346.         // Check for snooze
  347.         var snoozeTime;
  348.         if (aItem.parentItem != aItem) {
  349.             snoozeTime = aItem.parentItem.getProperty("X-MOZ-SNOOZE-TIME-"+aItem.recurrenceId.nativeTime)
  350.         } else {
  351.             snoozeTime = aItem.getProperty("X-MOZ-SNOOZE-TIME");
  352.         }
  353.  
  354.         const calIDateTime = Components.interfaces.calIDateTime;
  355.         if (snoozeTime && !(snoozeTime instanceof calIDateTime)) {
  356.             var time = Components.classes["@mozilla.org/calendar/datetime;1"]
  357.                                  .createInstance(calIDateTime);
  358.             time.icalString = snoozeTime;
  359.             snoozeTime = time;
  360.         }
  361. dump("snooze time is:"+snoozeTime+'\n');
  362.         alarmTime = alarmTime.clone();
  363.  
  364.         // Handle all day events.  This is kinda weird, because they don't have
  365.         // a well defined startTime.  We just consider the start/end to be 
  366.         // midnight in the user's timezone.
  367.         if (alarmTime.isDate) {
  368.             alarmTime = alarmTime.getInTimezone(this.mTimezone);
  369.             alarmTime.isDate = false;
  370.         }
  371.  
  372.         var offset = aItem.alarmOffset || aItem.parentItem.alarmOffset;
  373.  
  374.         alarmTime.addDuration(offset);
  375.         alarmTime = alarmTime.getInTimezone("UTC");
  376.         alarmTime = snoozeTime || alarmTime;
  377. dump("considering alarm for item:"+aItem.title+'\n offset:'+offset+', which makes alarm time:'+alarmTime+'\n');
  378.         var now;
  379.         // XXX When the item is floating, should use the default timezone
  380.         // from the prefs, instead of the javascript timezone (which is what
  381.         // jsDateToFloatingDateTime uses)
  382.         if (alarmTime.timezone == "floating")
  383.             now = jsDateToFloatingDateTime((new Date()));
  384.         else
  385.             now = jsDateToDateTime((new Date())).getInTimezone("UTC");
  386. dump("now is "+now+'\n');
  387.         var callbackObj = {
  388.             alarmService: this,
  389.             item: aItem,
  390.             notify: function(timer) {
  391.                 this.alarmService.alarmFired(this.item);
  392.                 delete this.alarmService.mEvents[this.item.id];
  393.             }
  394.         };
  395.  
  396.         if (alarmTime.compare(now) >= 0) {
  397. dump("alarm is in the future\n");
  398.             // We assume that future alarms haven't been acknowledged
  399.  
  400.             // delay is in msec, so don't forget to multiply
  401.             var timeout = alarmTime.subtractDate(now).inSeconds * 1000;
  402.  
  403.             var timeUntilRefresh = this.mRangeEnd.subtractDate(now).inSeconds * 1000;
  404.             if (timeUntilRefresh < timeout) {
  405. dump("alarm is too late\n");
  406.                 // we'll get this alarm later.  No sense in keeping an extra timeout
  407.                 return;
  408.             }
  409.  
  410.             this.mEvents[aItem.id] = newTimerWithCallback(callbackObj, timeout, false);
  411.             dump("adding alarm timeout (" + timeout + ") for " + aItem + "\n");
  412.         } else {
  413.             var lastAck = aItem.alarmLastAck || aItem.parentItem.alarmLastAck;
  414.             dump("Last ack was:"+lastAck+'\n');
  415.             // This alarm is in the past.  See if it has been previously ack'd
  416.             if (lastAck && lastAck.compare(alarmTime) >= 0) {
  417. dump(aItem.title+' - alarm previously ackd2\n');
  418.                 return;
  419.             } else { // Fire!
  420. dump("alarm is in the past, and unack'd, firing now!\n");
  421.                 this.alarmFired(aItem);
  422.             }
  423.         }
  424.     },
  425.  
  426.     removeAlarm: function(aItem) {
  427.         if (aItem.id in this.mEvents) {
  428.             this.mEvents[aItem.id].cancel();
  429.             delete this.mEvents[aItem.id];
  430.         }
  431.     },
  432.  
  433.     findAlarms: function() {
  434.         var getListener = {
  435.             alarmService: this,
  436.             onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail) {
  437.             },
  438.             onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
  439.                 for (var i = 0; i < aCount; ++i) {
  440.                     var item = aItems[i];
  441.                     if (this.alarmService.hasAlarm(item)) {
  442.                         this.alarmService.addAlarm(item);
  443.                     }
  444.                 }
  445.             }
  446.         };
  447.  
  448.         var now = jsDateToDateTime((new Date())).getInTimezone("UTC");
  449.  
  450.         var start;
  451.         if (!this.mRangeEnd) {
  452.             // This is our first search for alarms.  We're going to look for
  453.             // alarms +/- 1 month from now.  If someone sets an alarm more than
  454.             // a month ahead of an event, or doesn't start Sunbird/Lightning
  455.             // for a month, they'll miss some, but that's a slim chance
  456.             start = now.clone();
  457.             start.month -= 1;
  458.             start.normalize();
  459.         } else {
  460.             // This is a subsequent search, so we got all the past alarms before
  461.             start = this.mRangeEnd.clone();
  462.         }
  463.         var until = now.clone();
  464.         until.month += 1;
  465.         until.normalize();
  466.  
  467.         // We don't set timers for every future alarm, only those within 6 hours
  468.         var end = now.clone();
  469.         end.hour += kHoursBetweenUpdates;
  470.         end.normalize();
  471.         this.mRangeEnd = end.getInTimezone("UTC");
  472.  
  473.         var calendarManager = this.calendarManager;
  474.         var calendars = calendarManager.getCalendars({});
  475.         const calICalendar = Components.interfaces.calICalendar;
  476.         var filter = calICalendar.ITEM_FILTER_COMPLETED_ALL |
  477.                      calICalendar.ITEM_FILTER_CLASS_OCCURRENCES |
  478.                      calICalendar.ITEM_FILTER_TYPE_ALL;
  479.  
  480.         for each(var calendar in calendars) {
  481.             calendar.getItems(filter, 0, start, until, getListener);
  482.         }
  483.     },
  484.  
  485.     alarmFired: function(event) {
  486.         if (event.calendar.suppressAlarms)
  487.             return;
  488.  
  489.         this.notifyObservers("onAlarm", [event]);
  490.     }
  491. };
  492.